perm filename UNIT.SAI[REV,MUS] blob sn#290438 filedate 1977-06-25 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00013 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00003 00002	ENTRY
C00006 00003	DEFINE Speed_of_sound=343 ∂ In meters per second.
C00007 00004	INTERNAL RECORD_CLASS REV_UNIT(
C00008 00005	EXTERNAL INTEGER PROCEDURE incr_prime(
C00013 00006	INTERNAL REAL PROCEDURE get_spec(
C00015 00007	INTERNAL REAL PROCEDURE set_spec(
C00018 00008	   ELSE IF EQU(new_spec,"rate")
C00021 00009	   ELSE IF EQU(new_spec,"delay")
C00024 00010	INTERNAL RECORD_POINTER(REV_UNIT) PROCEDURE new_unit(
C00026 00011	INTERNAL PROCEDURE free_unit(
C00028 00012	INTERNAL PROCEDURE copy_unit(
C00030 00013	END   "UNIT"
C00038 ENDMK
C⊗;
ENTRY;
BEGIN "UNIT"
REQUIRE "HEADER.SAI" SOURCE_FILE;

∂ Ken Shoemake.  December 1976.
This module defines the concept of a single unit first-order all-pass
reverberator.  The concept is embodied in a RECORD_CLASS definition and
several associated manipulation procedures.  It is intended that any
use of the records be channeled thru the procedures so that the integrity
of the concept may be maintained.  In particular, the routines preserve
some invariances on the parameters of a unit.  The central routines are
GET_SPEC and SET_SPEC, which take as arguments a unit reverberator (i.e.,
an instance of the RECORD_CLASS) and a string naming the desired parameter.
Setting a parameter requires additional arguments to constrain the
invariants.  None of the routines keep OWN variables or use non-local
variables, so parameter lists may be slightly longer than otherwise.

The other procedures create or delete instances of units, or copy parameters
from one instance to another.  One auxiliary routine is used to find
the nearest prime to a given number -- however it returns an INDEX, not
a prime number, as its result.

Parameters to GET_ and SET_SPEC are:
    #samples	-INT, number of samples delay
    decay	-REAL, time for impulse to drop 60dB
    delay	-REAL, time around delay loop
    frequency	-REAL, 1.0/delay
    gain	-REAL, attenuation in delay loop
    rate	-INT, clock rate
    wall	-REAL, distance to nearest wall implied by delay

The convention for errors is to PRINT the name of the procedure in which
the error occurred followed by the legal range of the offending argument.
;
DEFINE Speed_of_sound=343; ∂ In meters per second.;
DEFINE DISTANCE_UNIT=⊂"meters"⊃;

REAL PROCEDURE LOG10(REAL X);
   RETURN(0.4342944819*LOG(X)); ∂ Log10(e)*Ln(x).;

INTERNAL RECORD_CLASS REV_UNIT(
   INTEGER NUMBER_OF_SAMPLES, CLOCK_RATE;
   REAL GAIN, DELAY_TIME, DECAY_TIME);

DEFINE DEFAULT_GAIN=⊂0.7071068⊃; ∂ Sqrt(2)/2.;
DEFINE DEFAULT_RATE=⊂25600⊃; ∂ Samples per second.;
DEFINE DEFAULT_DELAY=⊂0.01785⊃; ∂ For 10 feet to wall.;
DEFINE DEFAULT_DECAY=⊂0.356⊃; ∂ Consistent with above defaults.;
EXTERNAL INTEGER PROCEDURE incr_prime(
	INTEGER n, incr(0));
INTERNAL REAL PROCEDURE get_spec(
      RECORD_POINTER(REV_UNIT) unit;
      STRING which_spec);
∂ Takes as argument an instance of a unit reverberator and the name of one
of the associated parameters.  Returns the value for that parameter for
the given unit.
;
   BEGIN "get_spec"
   DEFINE error(range)=⊂PRINT(↓,"get_spec: ",range,↓)⊃;

   IF unit = NULL_RECORD
    THEN BEGIN
      error("unit ≠ null_record");
      RETURN(0);
      END;
   IF EQU(which_spec,"delay")
    THEN RETURN(REV_UNIT:DELAY_TIME[unit])
   ELSE IF EQU(which_spec,"gain")
    THEN RETURN(REV_UNIT:GAIN[unit])
   ELSE IF EQU(which_spec,"decay")
    THEN RETURN(REV_UNIT:DECAY_TIME[unit])
   ELSE IF EQU(which_spec,"#samples")
    THEN RETURN(REV_UNIT:NUMBER_OF_SAMPLES[unit])
   ELSE IF EQU(which_spec,"wall")
    THEN RETURN(REV_UNIT:DELAY_TIME[unit]*(Speed_of_sound/2))
   ELSE IF EQU(which_spec,"rate")
    THEN RETURN(REV_UNIT:CLOCK_RATE[unit])
   ELSE IF EQU(which_spec,"frequency")
    THEN RETURN(1./REV_UNIT:DELAY_TIME[unit])
   ELSE
    error("which_spec ε {#samples,decay,delay,frequency,gain,rate,wall}");
   END   "get_spec";
INTERNAL REAL PROCEDURE set_spec(
      RECORD_POINTER(REV_UNIT) unit;
      STRING new_spec, fix_spec;
      REAL spec_val;
      BOOLEAN use_prime(TRUE));
∂ The alter ego of GET_SPEC, this routine sets a parameter in the given
unit -- possibly changing others as a side-effect.  The "fix_spec" names
which parameter is to remain unchanged, and is one of GAIN, DELAY, or DECAY.
Normally, the number of samples delay is required to be prime.  However,
this can be overridden by specify "use_prime" to be FALSE.
;
   BEGIN "set_spec"
   DEFINE decay!(gain,delay)=⊂(-3.0*delay/LOG10(ABS gain))⊃,
      gain!(delay,decay)=⊂(0.001↑(delay/decay))⊃,
      delay!(decay,gain)=⊂(decay*LOG10(ABS gain)/(-3.0))⊃;
   DEFINE r_Rate=⊂REV_UNIT:CLOCK_RATE⊃,
      r_#Samps=⊂REV_UNIT:NUMBER_OF_SAMPLES⊃,
      r_Delay=⊂REV_UNIT:DELAY_TIME⊃,
      r_Decay=⊂REV_UNIT:DECAY_TIME⊃,
      r_Gain=⊂REV_UNIT:GAIN⊃;
   DEFINE error(range)=⊂PRINT(↓,"set_spec: ",range,↓)⊃;
   DEFINE check_gain=⊂IF ABS r_Gain[unit] ≤ .618    ∂ Phi = (SQRT(5.0)-1.0)/2.0;
    THEN PRINT(↓,"set_spec: Warning! |gain| > .618 for best results",↓)⊃;

   IF unit = NULL_RECORD
    THEN BEGIN
      error("unit ≠ null_record");
      RETURN(0);
      END;
   IF EQU(new_spec,"wall")
    THEN BEGIN
      IF spec_val < 0
       THEN error("wall ≥ 0")
       ELSE set_spec(unit,
         "delay",fix_spec,spec_val/(Speed_of_sound/2),
         use_prime);
      RETURN(get_spec(unit,"wall"));
      END
   ELSE IF EQU(new_spec,"frequency")
    THEN BEGIN
      IF spec_val = 0
       THEN error("frequency ≠ 0")
       ELSE set_spec(unit,
         "delay",fix_spec,1.0/spec_val,
         use_prime);
      RETURN(get_spec(unit,"frequency"));
      END
   ELSE IF EQU(new_spec,"rate")
    THEN BEGIN
      IF spec_val < 1
       THEN BEGIN
         error("rate ≥ 1");
         RETURN(get_spec(unit,"rate"));
         END;
      r_Rate[unit] ← spec_val;
      set_spec(unit,"delay",fix_spec,r_Delay[unit],use_prime);
      RETURN(spec_val);
      END
   ELSE IF EQU(new_spec,"gain")
    THEN BEGIN
      IF NOT(0 < ABS spec_val < 1)
       THEN BEGIN
         error("0 < |gain| < 1");
         RETURN(r_Gain[unit]);
         END;
      r_Gain[unit] ← spec_val;
      check_gain;
      IF EQU(fix_spec,"delay")
       THEN r_Decay[unit] ← decay!(r_Gain[unit],r_Delay[unit])
      ELSE IF EQU(fix_spec,"decay")
       THEN set_spec(unit,
         "delay","gain",delay!(r_Decay[unit],r_Gain[unit]),
         use_prime)
      ELSE BEGIN
         error("fix_spec ε {delay,decay}");
         set_spec(unit,
            "delay","gain",delay!(r_Decay[unit],r_Gain[unit]),
            use_prime);
         END;
      RETURN(spec_val);
      END
   ELSE IF EQU(new_spec,"decay")
    THEN BEGIN
      IF spec_val ≤ 0
       THEN BEGIN
         error("decay > 0");
         RETURN(r_Decay[unit]);
         END;
      r_Decay[unit] ← spec_val;
      IF EQU(fix_spec,"delay")
       THEN BEGIN
         r_Gain[unit] ← gain!(r_Delay[unit],r_Decay[unit])
               *(IF r_Gain[unit] ≥ 0 THEN 1 ELSE -1);
         check_gain;
         END
      ELSE IF EQU(fix_spec,"gain")
       THEN set_spec(unit,
         "delay","decay",delay!(r_Decay[unit],r_Gain[unit]),
         use_prime)
      ELSE BEGIN
         error("fix_spec ε {delay,gain}");
         r_Gain[unit] ← gain!(r_Delay[unit],r_Decay[unit])
            *(IF r_Gain[unit] ≥ 0 THEN 1 ELSE -1);
         check_gain;
         END;
      RETURN(spec_val)
      END
   ELSE IF EQU(new_spec,"delay")
    THEN BEGIN
      IF spec_val < 0
       THEN error("delay ≥ 0")
       ELSE set_spec(unit,
         "#samples",fix_spec,spec_val*r_Rate[unit],
         use_prime);
      RETURN(get_spec(unit,"delay"));
      END
   ELSE IF EQU(new_spec,"#samples")
    THEN BEGIN
      INTEGER int_val;
      IF spec_val < 0
       THEN BEGIN
         error("#samples ≥ 0");
         RETURN(r_#Samps[unit]);
         END;
         ∂ At this point, we're guaranteed to yield a consistent unit;
      int_val ← spec_val;  ∂ To get an integer type.;
      IF use_prime
       THEN int_val ← incr_prime(int_val);
      r_#Samps[unit] ← int_val;
      r_Delay[unit] ← int_val/r_Rate[unit];
      IF EQU(fix_spec,"gain")
       THEN r_Decay[unit] ← decay!(r_Gain[unit],r_Delay[unit])
      ELSE IF EQU(fix_spec,"decay")
       THEN BEGIN
            r_Gain[unit] ← gain!(r_Delay[unit],r_Decay[unit])
                  *(IF r_Gain[unit] ≥ 0 THEN 1 ELSE -1);
            check_gain;
            END
      ELSE BEGIN
         error("fix_spec ε {gain,decay}");
         r_Gain[unit] ← gain!(r_Delay[unit],r_Decay[unit])
            *(IF r_Gain[unit] ≥ 0 THEN 1 ELSE -1);
         check_gain;
         END;
      RETURN(int_val);
      END
   ELSE
    error("new_spec ε {#samples,decay,delay,frequency,gain,rate,wall}");
   END   "set_spec";

INTERNAL RECORD_POINTER(REV_UNIT) PROCEDURE new_unit(
      INTEGER rate(DEFAULT_RATE);
      REAL gain(DEFAULT_GAIN), delay(DEFAULT_DELAY);
      BOOLEAN use_prime(TRUE));
∂ Creates a new instance of a unit reverberator with the given initial
parameter values (if specified).  Naturally, it guarantees all the
necessary invariants for a unit.
;
   BEGIN "new unit"
   RECORD_POINTER(REV_UNIT) r;
   r←NEW_RECORD(REV_UNIT);
   IF rate = 0
    THEN rate ← DEFAULT_RATE;
   REV_UNIT:CLOCK_RATE[r]←rate;
   IF gain = 0
    THEN gain ← DEFAULT_GAIN;
   REV_UNIT:GAIN[r]←gain;
   IF delay = 0
    THEN delay ← DEFAULT_DELAY;
   set_spec(r,"delay","gain",delay,use_prime);
   RETURN(r);
   END   "new unit";
INTERNAL PROCEDURE free_unit(
      REFERENCE RECORD_POINTER(REV_UNIT) unit);
∂ Destroys an instance of a unit, recycling its associated storage.  There
had better be no pointers still pointing at this unit afterwards.  The
argument pointer is set to the NULL_RECORD.
;
   BEGIN "free unit"

   ∂ PROCEDURE TO CALL A RECORD'S HANDLER PROCEDURE;
   EXTERNAL RECORD_POINTER(ANY_CLASS) PROCEDURE $RECFN(
         INTEGER OP;
         RECORD_POINTER(ANY_CLASS) R);

   ∂ OP VALUES FOR $RECFN;
   DEFINE ALLOCATE_RECORD = 1;
   DEFINE MARK_SUBFIELDS = 4;
   DEFINE DELETE_RECORD = 5;

   IF
      unit = NULL_RECORD
    THEN
      RETURN;

   $RECFN(DELETE_RECORD,unit);
   unit ← NULL_RECORD;
   END   "free unit";
INTERNAL PROCEDURE copy_unit(
      REFERENCE RECORD_POINTER(REV_UNIT) new_copy;
      RECORD_POINTER(REV_UNIT) unit);
∂ Copies the parameters from "unit" to "new_copy", creating a unit instance
for "new_copy" if necessary.  This guarantees that the copy maintains all
the invariances, and saves on recycling and shared copies (the latter being
very hazardous).
;
   BEGIN "copy unit"
   IF
      unit = NULL_RECORD
    THEN
      RETURN;

   IF
      new_copy = NULL_RECORD
    THEN
      new_copy ← NEW_RECORD(REV_UNIT);

   REV_UNIT:NUMBER_OF_SAMPLES[new_copy] ←
      REV_UNIT:NUMBER_OF_SAMPLES[unit];
   REV_UNIT:CLOCK_RATE[new_copy] ←
      REV_UNIT:CLOCK_RATE[unit];
   REV_UNIT:GAIN[new_copy] ←
      REV_UNIT:GAIN[unit];
   REV_UNIT:DELAY_TIME[new_copy] ←
      REV_UNIT:DELAY_TIME[unit];
   REV_UNIT:DECAY_TIME[new_copy] ←
      REV_UNIT:DECAY_TIME[unit];
   END   "copy unit";
END   "UNIT"